/**
   Interface to gdb for debugging apps on top of the simulator.
*/

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "base/GDB.h"
#include "base/Simulator.h"
#include "gdb.h"

NEW_DELETE_OPERATORS_CPP(GDB,1);

/** 
    jmp_buf for recovering from gdb induced segmentation fauly
*/
jmp_buf gdbSIGSEGVJumpBuffer;

/**
   GDB::GDB
   
   Initialize the GDB module. 
*/
GDB::GDB () : 
    interactive(false), pendingIO(false), 
    breakNextInstructionCPU(-1), breakNextCycle(false), 
    listenFD(-1), hostFD(-1)
{
    gdbLog = NEW Logger("gdbLog", "gdb", "-:w", "gdb tracing");
    
    listenFD = socket(PF_INET, SOCK_STREAM, 0);
    gdbLog->d("GDB::GDB listenFD=%d\n", listenFD);
    ASSERT(listenFD != -1);
   
    /* 
       Allow rapid reuse of this port. 
    */
    int optval = 1;
    if (setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval)) < 0) {
        gdbLog->w("GDB::GDB setsockopt SO_REUSEADDR");
        /* 
           Not fatal 
        */
    }

    struct sockaddr_in sockaddr;
    memset((char *)&sockaddr, 0, sizeof(struct sockaddr_in));
 
    sockaddr.sin_family = PF_INET;
    sockaddr.sin_port = htons(global_sim->cfg.gdbPort);
    sockaddr.sin_addr.s_addr = INADDR_ANY;
   
    while (bind(listenFD, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) || listen(listenFD, 1)) {
        global_sim->cfg.gdbPort++;
        sockaddr.sin_port = htons(global_sim->cfg.gdbPort);
    }

    if (global_sim->cfg.gdbPort == 0) {
        socklen_t namelen = sizeof(sockaddr);
        int result = getsockname(listenFD, (struct sockaddr*)&sockaddr, &namelen);
        ASSERT(result == 0);
        ASSERT(namelen == sizeof(sockaddr));
        global_sim->cfg.gdbPort = ntohs(sockaddr.sin_port);
    }
    gdbLog->i("Listening for gdb on port %d\n", global_sim->cfg.gdbPort);
}

/**
   GDB::~GDB
   
   Cleanup the GDB module. 
*/
GDB::~GDB () {
    // if we are connected to the host, tell them goodbye
    if (hostFD != -1) {
        char buf[256];
        sim_sprintf(buf, "W%02x", global_sim->getExitCode());
        putpkt(buf);
    }
    
    // gdb
    delete gdbLog;
}
    
/**
   Convert hex digit A to a number.  
*/
static int fromhex (int a) {
    if (a >= '0' && a <= '9') {
        return a - '0';
    }
    if (a >= 'a' && a <= 'f') {
        return a - 'a' + 10;
    }
    if (a >= 'A' && a <= 'F') {
        return a - 'A' + 10;
    }
    Log->e("Request contains invalid hex digit a=%d\n", a);
    return 0;
}

/**
   Convert number NIB to a hex digit.  
*/
static int tohex (int nib) {
    if (nib < 10) {
        return '0' + nib;
    }
    return 'A' + nib - 10;
}

static void stringfromhex (const char* in, char* out) {
    while (in[0] != '\0') {
        char c = (fromhex(in[0]) * 16) + fromhex(in[1]);
        in += 2;
        out[0] = c;
        out++;
    }
    out[0] = '\0';
}

static void stringtohex (const char* in, char* out) {
    char* b = out;
    int len = strlen(in);
    for (int i = 0; i < len; i++) {
        char c = in[i];
        *b++ = tohex((c >> 4) & 0xf);
        *b++ = tohex( c       & 0xf);
    }
    *b = '\0';
}

static void convert_int_to_ascii (uint8 *from, char *to, int n) {
    while (n--) {
        char ch = *from++;
        int nib = ((ch & 0xf0) >> 4) & 0x0f;
        *to++ = tohex(nib);
        nib = ch & 0x0f;
        *to++ = tohex(nib);
    }
    *to++ = 0;
}


static void convert_ascii_to_int (char *from, uint8 *to, int n) {
    while (n--) {
        int nib1 = fromhex(*from++);
        int nib2 = fromhex(*from++);
        *to++ = (((nib1 & 0x0f) << 4) & 0xf0) | (nib2 & 0x0f);
    }
}

static char* findchar (char* s, int c) {
    while (true) {
        int c1 = *s;
        if ((c1 == c) || (! c1)) {
            return s;
        }
        s ++;
    }
    /*
      JeffG: This never gets executed (assuming s is NULL-terminated),
      but this is necessary to get it to compile without warnings 
    */
    return NULL; 
}

static void prepare_resume_reply (char *buf,unsigned int sig, unsigned int cpuno) {
    sim_sprintf(buf, "S%02x%02x", sig, cpuno);
}

static void write_ok (char *buf) {
    strcpy(buf, "OK");
}

static void write_error (char *buf, int value) {
    sim_sprintf(buf, "E%02x", value);
}

static void write_unknown (char *buf) {
    strcpy(buf, "");
}

static int read_cpu (char* buf) {
    return (fromhex(buf[0]) * 16) + fromhex(buf[1]);
}

/**
   Delcare register names based on platform specific gdb.h
*/
static const char* register_names[] = REGISTER_NAMES;

/**
   Delcare register lengths based on platform specific gdb.h
*/
static int register_lengths[] = REGISTER_LENGTHS;

#define MAX_XFER (8*1024)

static int read_memory (char* buf, VA memaddr, unsigned int len, unsigned int cpuno) {
    ASSERT(cpuno < global_sim->getNumCpus());
    if (len > MAX_XFER) {
        Log->e("GDB::read_memory: too many bytes requested");
    }
   
    uint8 readout_buf[MAX_XFER];
    if (global_sim->getMemory(cpuno, memaddr, len, readout_buf)) {
        convert_int_to_ascii(readout_buf, buf, len);
        return 0;
    }

    return 1;
}

static int write_memory (char* buf, VA memaddr, unsigned int len, unsigned int cpuno) {
    ASSERT(cpuno < global_sim->getNumCpus());
 
    if (len > MAX_XFER) {
        Log->e("GDB::write_memory: too many bytes requested");
    }

    uint8 readout_buf[MAX_XFER];
    convert_ascii_to_int(buf, readout_buf, len);
    if (global_sim->putMemory(cpuno, memaddr, len, readout_buf)) {
        return 0;
    }

    return 1;
}

/**
   GDB::run
   
   Main call to handle the simulator side of a gdb session.
*/
GDB_run_result GDB::run (int start_cpuno) {
    // BEWARE: thread_continue and thread_general vary 
    // from 1 through getNumCpus, 
    // not 0 through getNumCpus-1
    static int thread_continue = -1; 
    static int thread_general  = -1; 
    
    char cmd_buf[BUFSIZ+1];
    GDB_run_result ret = GDB_CONTINUE;
    
    gdbLog->d("GDB::run start_cpuno %d\n", start_cpuno);

    int sig = SIGTRAP;
    if (hostFD == -1) {
        /* 
           establishing a new connection.  GDB does not expect to see
           a reason-for-stop packet 'S' in this initial setup transaction.
        */
        ASSERT(pendingIO);
        pendingIO = false;
        gdbLog->i("\nEnter debugger, port = %d, waiting...\n", global_sim->cfg.gdbPort);
        socklen_t addrlen = sizeof(sockaddr_in);
        struct sockaddr_in sockaddr;
        hostFD = accept(listenFD, (struct sockaddr*)&sockaddr, (socklen_t*)&addrlen);
        if (hostFD == -1) {
            /* 
               could not get debugger 
            */
            gdbLog->w("Debugger accept failed\n");
            return GDB_CONTINUE;
        }
        gdbLog->w("gdb attached\n");
        /* 
           Tell TCP not to delay small packets.  This greatly speeds up
           interactive response.
        */
        int optval = 1;
        setsockopt(hostFD, IPPROTO_TCP, TCP_NODELAY,
                   (void*)&optval, sizeof(optval));
        gdbLog->d("GDB::run hostFD=%d\n", hostFD);
    }
    else {
        /* 
           reestablishing an old connection.  We might get here either
           for internal reasons (hitting a breakpoint) or for external
           reasons (gdb sent us a ^C).  If external, we need to swallow
           the break packet.  Then in either case, let GDB know why
           we stopped.
        */
        if (pendingIO) {
            pendingIO = false;
            /* 
               the only thing we expect to see when polling is the
               Control-C character... C=3
            */
            int ch = readchar();
            if (ch != '\003') {
                gdbLog->w("unexpected gdb input when expecting ^C: '%c'\n", ch);
            }
            /* 
               make gdb happy: it sent a ^C so it should get one back 
            */
            sig = SIGINT;
        }

        /* 
           Inform the remote debugger we have entered debugging mode. 
        */
        sim_sprintf(cmd_buf, "S%02x%02x", sig, start_cpuno);
        if (putpkt(cmd_buf) < 0) { 
            goto errout;
        }
    }


    while (true) {
        if (getpkt(cmd_buf) < 0) { 
            goto errout;
        }

        gdbLog->d("GETPKT: %s\n", cmd_buf);
      
        char ch = cmd_buf[0];
        gdbLog->d("gdb command %c\n", ch);
        switch (ch) {

        case '?': {
            // ? queries initial state on connection
            ASSERT(true);
            prepare_resume_reply(cmd_buf, sig, start_cpuno);
            break;
        }

        case 'c': {
            // c is for 'continue'
            ASSERT(true);
            ret = GDB_CONTINUE;
            goto out;
        }

        case 'D': {
            // D is sent on "detach"
            ASSERT(true);
            write_ok(cmd_buf);
            putpkt(cmd_buf);
            goto errout;
        }

        case 'g': {
            // g gets all registers. code from SimOS. still valid gdb command.
            ASSERT(false);
            unsigned int cpuno = read_cpu(cmd_buf+1);
            ASSERT(cpuno <= global_sim->getNumCpus());
            char* b = cmd_buf;
            for (int i = 0; i < GDB_NUM_REGS; i++) {
                uint8 r[MAX_REGISTER_LENGTH];
                int register_length = register_lengths[i];
                ASSERT(register_length <= MAX_REGISTER_LENGTH);
                if (!global_sim->getRegister(cpuno, i, register_length, r)) {
                    gdbLog->w("unexpected problem reading register %x(%d)=%s: %s\n", i, i, register_names[i], cmd_buf);
                    memset(r, 0, register_length);
                }
                convert_int_to_ascii(r, b, register_length); 
                b += register_length*2;
            }
            *b = 0;
            break;
        }

        case 'G': {
            // G sets all registers. code from SimOS. still valid gdb command.
            ASSERT(false);
            unsigned int cpuno = read_cpu(cmd_buf+1);
            ASSERT (cpuno <= global_sim->getNumCpus());
            bool succeeded = true;
            char* b = cmd_buf + 3;
            for (int i = 0; i < GDB_NUM_REGS; i++) {
                uint8 r[MAX_REGISTER_LENGTH];
                int register_length = register_lengths[i];
                ASSERT(register_length <= MAX_REGISTER_LENGTH);
                convert_ascii_to_int(b, r, register_length); 
                if (!global_sim->putRegister(cpuno, i, register_length, r)) {
                }
                b += register_length*2;
            }
            if (succeeded) {
                write_ok(cmd_buf);
            } 
            else {
                gdbLog->w("unexpected gdb problem writing all registers: %s\n", cmd_buf);
                write_unknown(cmd_buf);
            }
            break;
        }

        case 'H': {
            // H is used to request a specific thread
            ASSERT(true);
            const char* dummy;
            int thread = sim_atoi(cmd_buf+2, &dummy);
            if (thread == 0) {
                thread = start_cpuno+1;
            }
            if (cmd_buf[1] == 'c') {
                thread_continue = thread;
                write_ok(cmd_buf);
            }
            else if (cmd_buf[1] == 'g') {
                thread_general = thread;
                write_ok(cmd_buf);
            }
            else {
                gdbLog->w("unexpected gdb here command: %s\n", cmd_buf);
                write_unknown(cmd_buf);
            }
            break;
        }

        case 'i': {
            // step a single cycle. code from SimOS. still valid gdb command. unknown how to use...
            ASSERT(false);
            /* 
               single step one cycle, then stop no matter what 
            */
            breakNextCycle = true;
            ret = GDB_CONTINUE;
            goto out;
        }

        case 'k': {
            // k is for "kill"
            ASSERT(true);
            exit(0);
            break;
        }

        case 'm': {
            // m is to read memory
            ASSERT(true);
            Reg64 memaddr;
            unsigned int len;
         
            int count = sscanf(cmd_buf, "m%8llx,%x",&memaddr,&len);
            ASSERT(count == 2);
            if (read_memory(cmd_buf, memaddr, len, thread_general-1)) {
                gdbLog->d("gdb problem reading memory: %s\n", cmd_buf);
                write_unknown(cmd_buf);
            }
            break;
        }

        case 'M': {
            // m is to write memory
            ASSERT(true);
            Reg64 memaddr;
            unsigned int len;
            char* spkt = findchar(cmd_buf, ':');
            ASSERT(*spkt == ':');
            int count = sscanf(cmd_buf, "M%8llx,%x", &memaddr,&len);
            ASSERT(count == 2);
            bool succeeded = write_memory(spkt+1, memaddr, len, thread_general-1); 
            if (succeeded) {
                write_ok(cmd_buf);
            } 
            else {
                gdbLog->w("unexpected gdb problem writing memory: %s\n", cmd_buf);
                write_error(cmd_buf, 0);
            }
            break;
        }

        case 'p': {
            // p is to read a register
            ASSERT(true);
            ASSERT(1 <= thread_general && thread_general <= (int)global_sim->getNumCpus());

            int regnum = fromhex(cmd_buf[1]);
            if (cmd_buf[2] != '\0') {
                regnum = (regnum * 16) + fromhex(cmd_buf[2]);
            }
            uint8 r[MAX_REGISTER_LENGTH];
            int register_length = register_lengths[regnum];
            ASSERT(register_length <= MAX_REGISTER_LENGTH);
            if (!global_sim->getRegister(thread_general-1, regnum, register_length, r)) {
                gdbLog->w("unexpected problem reading register %x(%d)=%s: %s\n", regnum, regnum, register_names[regnum], cmd_buf);
                memset(r, 0, register_length);
            }
            convert_int_to_ascii(r, cmd_buf, register_length); 
            gdbLog->d("get register: regnum=%x(%d) %s=%s\n", regnum, regnum, register_names[regnum], cmd_buf);
            cmd_buf[register_length*2] = '\0';
            break;
        }

        case 'P': {
            // P is to write a register
            ASSERT(true);
            int regnum = fromhex(cmd_buf[1]);
            int valueIndex;
            if (cmd_buf[2] == '=') {
                valueIndex = 3;
            }
            else {
                valueIndex = 4;
                regnum = (regnum * 16) + fromhex(cmd_buf[2]);
            }
            char* b = cmd_buf + valueIndex;

            bool succeeded = true;

            uint8 r[MAX_REGISTER_LENGTH];
            int register_length = register_lengths[regnum];
            ASSERT(register_length <= MAX_REGISTER_LENGTH);
            convert_ascii_to_int(b, r, register_length); 
            if (!global_sim->putRegister(thread_general-1, regnum, register_length, r)) {
            }
            if (succeeded) {
                write_ok(cmd_buf);
            } 
            else {
                gdbLog->w("unexpected gdb problem writing register: %s\n", cmd_buf);
                write_error(cmd_buf, 0);
            }
            break;
        }

        case 'q': {
            // qC
            if (strcmp(cmd_buf, "qC") == 0) {
                strcpy(cmd_buf, "QC");
                sim_sprintf(cmd_buf+2, "%04x", start_cpuno+1);
            }
            // qOffsets
            else if (strcmp(cmd_buf, "qOffsets") == 0) {
                // Supposed to return something like:
                // Text=xxx;Data=yyy;Bss=zzz
                strcpy(cmd_buf, "");
            }
            // qfThreadInfo is from "info threads"
            else if (strcmp(cmd_buf, "qfThreadInfo") == 0) {
                strcpy(cmd_buf, "m");
                char *b = cmd_buf+1;
                int l = global_sim->getNumCpus();
                for (int i = 1; i <= l; i++) {
                    b += sim_sprintf(b, 
                                     "%d%s", 
                                     i,
                                     i == l ? "" : ",");
                }
            }
            // qsThreadInfo is from "info threads"
            else if (strcmp(cmd_buf, "qsThreadInfo") == 0) {
                strcpy(cmd_buf, "l");
            }
            // qThreadExtraInfo,id is from "info threads"
            else if (strncmp(cmd_buf, "qThreadExtraInfo,", strlen("qThreadExtraInfo,")) == 0) {
                int threadID;
                int count = sscanf(cmd_buf, "qThreadExtraInfo,%d", &threadID);
                ASSERT(count == 1);
                const char* status = global_sim->getStatus(threadID-1);
                stringtohex(status, cmd_buf);
            }
            // qSymbol:: was seen... don't know how to force it
            else if (strcmp(cmd_buf, "qSymbol::") == 0) {
                // just tell host we don't need to look up any symbols
                write_ok(cmd_buf);
            }
            // qRcmd,command is from "monitor instr_count"
            else if (strncmp(cmd_buf, "qRcmd,", strlen("qRcmd,")) == 0) {
                char remote_cmd_buf[BUFSIZ+1];
                stringfromhex(cmd_buf + strlen("qRcmd,"), 
                              remote_cmd_buf);
                gdbLog->d("gdb monitor command: %s\n", remote_cmd_buf);
                if (strncmp(remote_cmd_buf, "instr_count", strlen("instr_count")) == 0) {
                    int cpu = -1;
                    int count = sscanf(remote_cmd_buf, "instr_count %d", &cpu);
                    if (count != 1) {
                        GDBPrint("usage: monitor instr_count <CPU>\n");
                    }
                    else if (cpu < 0) {
                        GDBPrint(" cpu argument for monitor instr_count too small: %d\n", cpu);
                    }
                    else if (cpu >= (int)global_sim->getNumCpus()) {
                        GDBPrint(" cpu argument for monitor instr_count too big: %d\n", cpu);
                    }
                    else {
                        GDBPrint("Instruction count for cpu %d: %lld\n", cpu, global_sim->getInstructionsExecuted(cpu));
                    }
                }
                else {
                    GDBPrint("unexpected gdb monitor command: %s\n", remote_cmd_buf);
                    GDBPrint("Available command: instr_count\n");
                }
                write_ok(cmd_buf);
            }
            else {
                gdbLog->w("unexpected gdb query command: %s\n", cmd_buf);
                write_unknown(cmd_buf);
            }
            break;
        }

        case 's': {
            if (1 <= thread_continue && thread_continue <= (int)global_sim->getNumCpus()) {
                ret = (GDB_run_result)(thread_continue-1);
            } 
            else {
                gdbLog->w("Single-step request for bad cpu or SS not implemented, continuing...\n");
                ret = GDB_CONTINUE;
            }
            goto out;
        }

        case 'v': {
            // vCont
            if (strcmp(cmd_buf, "vCont?") == 0) {
                strcpy(cmd_buf, "");
            }
            else {
                gdbLog->w("unexpected gdb verbose command: %s\n", cmd_buf);
                write_unknown(cmd_buf);
            }
            break;
        }

        case 'z':
        case 'Z': {
            /*
              Set   breakpoint:  "z0,<addr>,<length>"
              Clear breakpoint:  "Z0,<addr>,<length>"
            */
            if (cmd_buf[1] == '0') {
                Reg breakaddr;
                size_t breaklen;
                int count = sscanf(cmd_buf + 1, "0,%8llx,%d", &breakaddr, &breaklen);
                ASSERT(count == 2);
                ASSERT(breaklen == 4);
                if (cmd_buf[0] == 'Z') {
                    addBreakpoint(breakaddr);
                }
                else if (cmd_buf[0] == 'z') {
                    deleteBreakpoint(breakaddr);
                }
                else {
                    ASSERT(false);
                }
                write_ok(cmd_buf);
            }
            else {
                gdbLog->w("unexpected gdb breakpoint command: %s\n", cmd_buf);
                write_unknown(cmd_buf);
            }
            break;
        }

        default:
            gdbLog->w("unexpected gdb command: %s\n", cmd_buf);
            strcpy(cmd_buf, "");
            break;
        }

        if (putpkt(cmd_buf) < 0) { 
            goto errout;
        }
    }

 out:
    return ret;

 errout:
    /* 
       the connection was terminated prematurely.  Reset 
    */
    close(hostFD);
    hostFD = -1;
    gdbLog->w("Remote debugger connection lost, continuing...\n");
    return GDB_CONTINUE;
}

/**
   GDB::readchar

   Returns next char from host GDB.  -1 if error.  
*/
int GDB::readchar () {
    static char buf[BUFSIZ];
    static int bufcnt = 0;
    static char *bufp;

    if (bufcnt-- > 0) {
        return *bufp++ & 0x7f;
    }

    bufcnt = read(hostFD, buf,  BUFSIZ);
    if (bufcnt <= 0) {
        gdbLog->w("GDB::readchar hostFD=%d\n", hostFD);
        return -1;
    }

    bufp = buf;
    bufcnt--;
    return *bufp++ & 0x7f;
}

/**
   GDB::putpkt

   Send a packet to the remote machine, with error checking.

   The data of the packet is in BUF.  Returns >= 0 on success, -1 otherwise. 
*/
int GDB::putpkt (char *buf) {
    /* 
       Copy the packet into buffer BUF2, encapsulating it
       and giving it a checksum.  
    */
    char buf2[2000];
    char* p = buf2;
    *p++ = '$';

    unsigned char csum = 0;
    int cnt = strlen(buf);
    for (int i = 0; i < cnt; i++) {
        csum += buf[i];
        *p++ = buf[i];
    }
   
    *p++ = '#';
    *p++ = tohex((csum >> 4) & 0xf);
    *p++ = tohex(csum & 0xf);

    /* 
       Send it over and over until we get a positive ack.  
    */
    char buf3[1];
    do {
        int cc;
      
        gdbLog->d("PUTPKT: %s\n", buf);
        if (write(hostFD, buf2, p - buf2) != p - buf2) {
            gdbLog->w("putpkt(write)");
            goto errout;
        }

        cc = read(hostFD, buf3, 1);
        if (cc <= 0) {
            gdbLog->w("putpkt(read)");
            goto errout;
        }
    } while (buf3[0] != '+');
   
    /* 
       Success! 
    */
    return 1;                   
   
 errout:
    return -1;
}

/**
   GDB::getpkt
   
   Read a packet from the remote machine, with error checking,
   and store it in BUF.  Returns length of packet, or negative if error. 
*/
int GDB::getpkt (char *buf) {
    char* bp = buf;
    int ignore_result;
    while (true) {
        unsigned char csum = 0;
      
        int c;
        while (true) {
            c = readchar();
            if (c == '$') {
                break;
            }
            if (c < 0) {
                goto errout; 
            }
        }
      
        bp = buf;
        while (true) {
            c = readchar();
            if (c < 0) {
                goto errout; 
            }
            if (c == '#') {
                break;
            }
            *bp++ = c;
            csum += c;
        }
        *bp = 0;
      
        c = readchar();
        if (c < 0) { 
            goto errout;
        }
        unsigned char c1 = fromhex(c);

        c = readchar();
        if (c < 0) { 
            goto errout;
        }
        unsigned char c2 = fromhex(c);
        if (csum == (c1 << 4) + c2) {
            break;
        }

        // TODO(mbarrien): Ignoring result of write
        ignore_result =write(hostFD, "-", 1);
    }
   
    // TODO(mbarrien): Ignoring result of write
    ignore_result = write(hostFD, "+", 1);
    return bp - buf;
   
 errout:
    return -1;
}

/**
   GDBPrint -- Like Logger::output, but sends it's data through the GDB interface
   
   Returns 0 if successful, -1 otherwise
*/
int GDB::GDBPrint (const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);

    char bufferText[BUFSIZ+1];
    sim_vsnprintf(bufferText, BUFSIZ, fmt, ap);
    gdbLog->d("GDBPrint %s", bufferText);
    char bufferHex[BUFSIZ+1];
    bufferHex[0] = 'O';
    stringtohex(bufferText, bufferHex+1);
    if (putpkt(bufferHex) < 0) {
        return -1;
    }
    return 0;
}

/**
   pollfd

   returns hostFD if there is a connection otherwise returns listenFD.
*/
int GDB::pollfd () {
    if (hostFD != -1) {
        return hostFD;
    } 
    return listenFD;
}

/**
   Check debugger connection to see whether it requires service.
   
   If so, mark state so that Processor will stop
*/
void GDB::poll (bool blocking) {
    int pollFD = pollfd();
    if (pollFD == -1) {
        return;
    }

    fd_set reads;
    FD_ZERO(&reads);
    FD_SET(pollFD, &reads);

    struct timeval timeout;
    timeout.tv_sec  = 0;
    timeout.tv_usec = 0;

    int result = select(pollFD+1, &reads, NULL, NULL, blocking ? NULL : &timeout);
    ASSERT(result != -1);
    if (result == 0) {
        return;
    }
    bool isset = (FD_ISSET(pollFD, &reads));
    if (!isset) {
        return;
    }
    
    /* 
       gdb sent us something.
    */
    gdbLog->d("GDB::poll saw data on debugger port %d\n", pollFD);
    interactive = true;
    pendingIO   = true;
    breakNextInstructionCPU = -1; // -1 => stop at any CPU
}

bool GDB::breakpointHit (VA va) {
    HashSet<VA>::const_iterator it = breakpoints.find(va);
    return it != breakpoints.end();
}

void GDB::addBreakpoint (VA va) {
    breakpoints.insert(va);
}

void GDB::deleteBreakpoint (VA va) {
    breakpoints.erase(va);
}
